package br.usp.wta.inductor;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import br.usp.language.automata.State;
import br.usp.language.automata.StateMachine;
import br.usp.language.automata.Transition;
import br.usp.language.automata.State.StateType;

/**
 * Grammar induction first phase
 * pg 55, Ivone 
 * 
 */
public class PrefixSufixGenerator {
    
    private File posTrain;
    
    private StateMachine prefixAut;
    private StateMachine sufixAut;
    
    private int nextStateName; // just an auxiliary variable to generate Ap
    
    /**
     * 
     * @param pos filename of positive examples
     * @param neg filename of negative examples
     */
    public PrefixSufixGenerator(String pos, String neg) {
        
        posTrain = new File(pos);
    }
    
    public void generate() throws IOException {
        
        try {
            this.generatePrefixAut();
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        this.generateSufixAut();
    }
    
    private void generatePrefixAut() throws IOException {
        
        this.prefixAut = new StateMachine("Ap");
        this.nextStateName = 1;
        
        BufferedReader br = new BufferedReader(new FileReader(posTrain));
        while (br.ready()) {
            String line = br.readLine();
            String[] symbols = line.split(" ");
            this.parsePosLine(symbols);
        }
    }
    
    private void parsePosLine(String[] symbols) {
        
        // it will parse a sentence (symbols sequence) and change the automata
        // so the automata will recognize this sentence
        
        State state = this.prefixAut.getInitialState();
        
        for (int i=0; i<symbols.length; i++) {

            String symbol = symbols[i];
            
            // searches for a transition to the symbol
            boolean hasTransition = false;
            for (Transition t: state.getTransitions()) {
                if (t.getConditions().contains(symbol)) {
                    hasTransition = true;
                    state = t.getStateTo();
                }
            }
            
            // creates a new transition and a new state, if necessary
            if (!hasTransition) {
                
                State snew = this.prefixAut.createState("s" + this.nextStateName++);
                state.createTransitionTo(snew, symbol, null);
                state = snew;
            }
            
            // último estado é de aceitação
            if (i == symbols.length - 1) {
                
                state.setAcceptState();
            }
        }
    }

    private void generateSufixAutOld() {
        
        // suffix automata is the ""inverse"" of the prefix automata
        this.sufixAut = new StateMachine("As");
        this.copyStatesAndTransitions(this.prefixAut, this.sufixAut);
        
        for (Transition t: this.sufixAut.getAllTransitions()) {
            t.invert();
        }

        // final state are no more final states
        List<State> finalStates = new ArrayList<State>();
        for (State s: this.sufixAut.getAllStates()) {
            if (s.isAcceptState()) {
                s.setStateType(StateType.NORMAL_STATE);
                finalStates.add(s);
            }
        }
        
        // initial state becomes a final state
        this.sufixAut.getInitialState().setAcceptState();

        // create new initial state (with links to the previous final states)
        State initial = this.sufixAut.createState("Sini");
        this.sufixAut.setInitialState(initial);
        for (State s: finalStates) {
            // in true, our automata implementation doen'st support such indeterminism!
            initial.createEpsilonTransitionTo(s, null);
        }
    }
    
    private void generateSufixAut() throws IOException {

        // suffix automata is the ""inverse"" of the prefix automata
        this.sufixAut = new StateMachine("As");
        
        this.nextStateName = 1;
        
        BufferedReader br = new BufferedReader(new FileReader(posTrain));
        while (br.ready()) {
            String line = br.readLine();
            String[] symbolsTemp = line.split(" ");
            List<String> symbols = Arrays.asList(symbolsTemp);
            Collections.reverse(symbols);
            this.parsePosLineAs(symbols);
        }

        for (Transition t: this.sufixAut.getAllTransitions()) {
            t.invert();
        }

        // final state are no more final states
        List<State> finalStates = new ArrayList<State>();
        for (State s: this.sufixAut.getAllStates()) {
            if (s.isAcceptState()) {
                s.setStateType(StateType.NORMAL_STATE);
                finalStates.add(s);
            }
        }
        
        // initial state becomes a final state
        this.sufixAut.getInitialState().setAcceptState();

        // create new initial state (with links to the previous final states)
        State initial = this.sufixAut.createState("Sini");
        this.sufixAut.setInitialState(initial);
        for (State s: finalStates) {
            // in true, our automata implementation doen'st support such indeterminism!
            initial.createEpsilonTransitionTo(s, null);
        }        
    }
    
   private void parsePosLineAs(List<String> symbols) {
        
        // it will parse a sentence (symbols sequence) and change the automata
        // so the automata will recognize this sentence
        
        State state = this.sufixAut.getInitialState();
        
        for (int i=0; i<symbols.size(); i++) {

            String symbol = symbols.get(i);
            
            // searches for a transition to the symbol
            boolean hasTransition = false;
            for (Transition t: state.getTransitions()) {
                if (t.getConditions().contains(symbol)) {
                    hasTransition = true;
                    state = t.getStateTo();
                }
            }
            
            // creates a new transition and a new state, if necessary
            if (!hasTransition) {
                
                State snew = this.sufixAut.createState("s" + this.nextStateName++);
                state.createTransitionTo(snew, symbol, null);
                state = snew;
            }
            
            // último estado é de aceitação
            if (i == symbols.size() - 1) {
                
                state.setAcceptState();
            }
        }
    }    

    // TODO: should be a clone from Automata
    private void copyStatesAndTransitions(StateMachine from, StateMachine to) {

        // copy states
        for (State s: from.getAllStates()) {
            State snew = to.createState(s.getName());
            if (s.isAcceptState())
                snew.setAcceptState();
        }
        // copy transitions
        for (State s: from.getAllStates()) {
            to.goToState(s.getName());
            State sfrom = to.getCurrentState();
            for (Transition t: s.getTransitions()) {
                to.goToState(t.getStateTo().getName());
                State sto = to.getCurrentState();
                sfrom.createTransitionTo(sto, t.getConditions(), null);
            }
        }

    }

    public StateMachine getPrefixAut() {
        
        return this.prefixAut;
    }
    
    public StateMachine getSufixAut() {
        
        return this.sufixAut;
    }    
}
